package com.chenfan.ccp.autoconfigure;

import com.chenfan.ccp.plug.mq.config.MqRegisterConfigure;
import com.chenfan.ccp.util.start.ApplicationContextUtil;
import com.chenfan.ccp.util.tool.JsonUtil;
import javassist.*;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.AttributeInfo;
import javassist.bytecode.ConstPool;
import javassist.bytecode.SignatureAttribute;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.EnumMemberValue;
import javassist.bytecode.annotation.StringMemberValue;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;

import java.util.*;

/**
 * 自动注册mq的发送和解析
 * @see com.chenfan.ccp.plug.mq.annotation.MqMessageListener
 * 的注解
 */
@Configuration
public class GlobalMqMessageConfigure extends AbstractAutoConfigure implements BeanDefinitionRegistryPostProcessor {

    private static final String[] PROPERTIES_FILE_ARR = new String[]{"application.yml", "bootstrap.yml"};

    private static Properties properties;

    static {

        YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
        List<ClassPathResource> fileList = new ArrayList<>();
        for (String fileName : PROPERTIES_FILE_ARR) {

            try {
                ClassPathResource classPathResource = new ClassPathResource(fileName);
                fileList.add(classPathResource);
            } catch (Exception e) {
            }

        }
        try {
            yaml.setResources(fileList.toArray(new ClassPathResource[fileList.size()]));
            properties = yaml.getObject();


        } catch (Exception e) {

        }
        Object active = properties == null ? ApplicationContextUtil.getProfile() : properties.get("spring.profiles.active");

        if (active != null) {
            try {

                String activeStr = active.toString();

                if (activeStr.indexOf(":") > 1) {
                    activeStr = activeStr.substring(activeStr.indexOf(":") + 1, activeStr.length() - 1);
                }

                ClassPathResource classPathResource = new ClassPathResource("application-" + activeStr + ".yml");
                if (properties == null) {
                    fileList.clear();
                }
                fileList.add(classPathResource);
                yaml.setResources(fileList.toArray(new ClassPathResource[fileList.size()]));
                properties = yaml.getObject();
            } catch (Exception e) {

            }

        }


    }

    private String parseEl(String el) {
        String val = ApplicationContextUtil.parseEl(el, properties);
        return val == null ? el : val;
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {


        String mqAliasBeanName = MqRegisterConfigure.getMqAliasBeanName();
        String[] aliases = beanDefinitionRegistry.getAliases(mqAliasBeanName);

        for (String metaInfo : aliases) {
            String canonicalName = metaInfo.substring(0, metaInfo.indexOf(MqRegisterConfigure.LINK));
            Map<String, Object> metaMap = JsonUtil.string2Obj(metaInfo.substring(metaInfo.indexOf(MqRegisterConfigure.LINK) + 1), Map.class);

            //解析el
            Map<String, Object> parseMetaMap = new HashMap<>();
            for (Map.Entry<String, Object> entry : metaMap.entrySet()) {
                String elKey = entry.getValue() + "";
                String newVal = parseEl(elKey);
                if (newVal.equals(elKey)) {
                    parseMetaMap.put(entry.getKey(), entry.getValue());
                } else {
                    parseMetaMap.put(entry.getKey(), newVal);
                }

            }

            Class<?> aClass = tryRegisterRocketMqListener(canonicalName, parseMetaMap);
            if (aClass == null) {
                continue;
            }

            beanDefinitionRegistry.registerBeanDefinition(aClass.getCanonicalName(),
                    BeanDefinitionBuilder.genericBeanDefinition(aClass).getBeanDefinition());


        }

    }


    //适配rocketMq
    private Class<?> tryRegisterRocketMqListener(String canonicalName, Map<String, Object> metaMap) {
        try {
            String className = canonicalName + "$impl";

            try {
                //检测是否存在
                Class.forName(className);
                return null;
            } catch (ClassNotFoundException e) {

            }
            ClassPool cp = ClassPool.getDefault();
            ClassClassPath classPath = new ClassClassPath(this.getClass());
            cp.insertClassPath(classPath);

            CtClass parentClass = cp.get(canonicalName);


            CtMethod consume = parentClass.getDeclaredMethod("consume");
            CtClass genericClass = consume.getParameterTypes()[0];

            CtClass listener = cp.get("org.apache.rocketmq.spring.core.RocketMQListener");

            CtClass subClass = cp.makeClass(className);

            subClass.setSuperclass(parentClass);
            subClass.addInterface(listener);

            SignatureAttribute.ClassSignature classSignature = new SignatureAttribute.ClassSignature(null,
                    new SignatureAttribute.ClassType(canonicalName),
                    // Set interface and its generic params
                    new SignatureAttribute.ClassType[]{new SignatureAttribute.ClassType("org.apache.rocketmq.spring.core.RocketMQListener",
                            new SignatureAttribute.TypeArgument[]{new SignatureAttribute.TypeArgument(new
                                    SignatureAttribute.ClassType(genericClass.getName()))}
                    )});

            subClass.setGenericSignature(classSignature.encode());

            ConstPool constPool = subClass.getClassFile().getConstPool();


            AnnotationsAttribute annotationsAttribute = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
            Annotation scopeAnnotation = new Annotation("org.apache.rocketmq.spring.annotation.RocketMQMessageListener", constPool);
            Object topic = metaMap.get("topic");

            Objects.requireNonNull(topic, "topic not null");

            scopeAnnotation.addMemberValue("topic", new StringMemberValue(topic.toString(), constPool));


            Boolean isRepeatedRemoteConsume = (Boolean) metaMap.get("isRepeatedRemoteConsume");

            Objects.requireNonNull(isRepeatedRemoteConsume, "isRepeatedRemoteConsume not null");


            Boolean isRepeatedLocalConsume = (Boolean) metaMap.get("isRepeatedLocalConsume");

            Objects.requireNonNull(isRepeatedLocalConsume, "isRepeatedLocalConsume not null");

            scopeAnnotation.addMemberValue("consumerGroup", new StringMemberValue(isRepeatedLocalConsume ? System.nanoTime() + "" : topic + "" + isRepeatedLocalConsume, constPool));

            EnumMemberValue enumMemberValue = new EnumMemberValue(constPool);
            enumMemberValue.setType("org.apache.rocketmq.spring.annotation.MessageModel");
            String transferModel = (String) metaMap.get("transferModel");

            enumMemberValue.setValue(transferModel.equals("P2P") ? "CLUSTERING" : "BROADCASTING");

            scopeAnnotation.addMemberValue("messageModel", enumMemberValue);

            annotationsAttribute.addAnnotation(scopeAnnotation);

            Annotation componentAnnotation = new Annotation("org.springframework.stereotype.Component", constPool);

            annotationsAttribute.addAnnotation(componentAnnotation);


            subClass.getClassFile().addAttribute(annotationsAttribute);


            CtMethod method;
            CtMethod implementMethod;
            if (isRepeatedRemoteConsume) {
                CtField ctField = CtField.make("org.springframework.data.redis.core.RedisTemplate redisTemplate;"
                        , subClass);

                List<AttributeInfo> attributeInfos = ctField.getFieldInfo().getAttributes();
                annotationsAttribute = !attributeInfos.isEmpty() ? (AnnotationsAttribute) attributeInfos.get(0) :
                        new AnnotationsAttribute(ctField.getFieldInfo().getConstPool(), AnnotationsAttribute.visibleTag);
                Annotation annotation = new Annotation("org.springframework.beans.factory.annotation.Autowired",
                        ctField.getFieldInfo().getConstPool());
                annotationsAttribute.addAnnotation(annotation);
                ctField.getFieldInfo().addAttribute(annotationsAttribute);

                subClass.addField(ctField);

                String methodBody = " if (this.redisTemplate == null) {\n" +
                        "            super.consume(message);\n" +
                        "        } else {\n" +
                        "            String redisKey = \"chenfan:mq:\" + com.chenfan.ccp.util.ApplicationContextUtil.getServiceName() + message;\n" +
                        "            Boolean var2 = this.redisTemplate.opsForValue().setIfAbsent(redisKey\n" +
                        "                    , com.chenfan.ccp.util.ApplicationContextUtil.getLocalIp(),2L, java.util.concurrent.TimeUnit.SECONDS);\n" +
                        "            if (Boolean.TRUE.equals(var2)) {\n" +
                        "                super.consume(message);\n" +
                        "                this.redisTemplate.delete(redisKey);\n" +
                        "\n" +
                        "            }\n" +
                        "\n" +
                        "        }\n" +
                        "    }";
                method = CtNewMethod.make("    public void onMessage(Object  obj) {\n" +
                        "\n" + genericClass.getName() + " message=(" + genericClass.getName() + ")obj;\n" + methodBody, subClass);

                implementMethod = CtNewMethod.make("    public void onMessage(" + genericClass.getName() + " message) {\n"
                        + methodBody, subClass);


            } else {
                String methodBody = "      this.consume(message);\n" +
                        "    }";
                method = CtNewMethod.make(
                        "    public void onMessage(" + genericClass.getName() + " obj) {\n" +
                                "\n" + genericClass.getName() + " message=(" + genericClass.getName() + ")obj;" +
                                methodBody, subClass);

                implementMethod = CtNewMethod.make("    public void onMessage(Object message) {\n" +
                        methodBody, subClass);

            }

            if (!genericClass.getName().equals(Object.class.getCanonicalName())) {
                subClass.addMethod(implementMethod);
            }


            subClass.addMethod(method);

//            subClass.writeFile("target/classes");
            Class<?> aClass = subClass.toClass();
            return aClass;
        } catch (Exception e) {
            System.err.println("mq load error "+e.getMessage());
            return null;
        }

    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

    }
}
